package cn.hejiang.mamis.core.service.impl;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.Cache;
import org.springframework.cache.Cache.ValueWrapper;
import org.springframework.cache.CacheManager;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.stereotype.Service;

import cn.hejiang.mamis.core.constant.Constants;
import cn.hejiang.mamis.core.entity.PlatSys;
import cn.hejiang.mamis.core.entity.PlatUser;
import cn.hejiang.mamis.core.entity.SessionDetails;
import cn.hejiang.mamis.core.service.PlatSysService;
import cn.hejiang.mamis.core.service.PlatUserService;
import cn.hejiang.mamis.core.service.ContextService;
import cn.hejiang.mamis.core.util.TokenUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;

@Service
@Slf4j
public class ContextServiceImpl implements ContextService {
	static final String SPLIT = "$_$";

	@Value("${server.servlet.session.timeout:3600}")
	private int SessionTimeOut;

	@Value("${open.api.getUserEmailByApikeyURL:}")
	String getUserEmailByApikeyURL;

	@Autowired
	PlatUserService platUserService;

	@Autowired
	PlatSysService platSysService;

	@Autowired
	CacheManager cacheManager;

	private static ThreadLocal<SessionDetails> _th_session = new ThreadLocal<>();
	private static ThreadLocal<PlatSys> _th_platsys = new ThreadLocal<>();

	public SessionDetails getSessionFromRequest(HttpServletRequest request) {
		// get from session
		final HttpSession session = request.getSession(false);
		if (session != null) {
			SessionDetails details = (SessionDetails) session.getAttribute(Constants.SESSION_DETAILS);
			if (details != null) {
				_th_session.set(details);
				return details;
			}
		}

		// get from x-api-key
		SessionDetails details = authenticateByApiKey(request);
		if (details != null) {
			_th_session.set(details);
			return details;
		}

		// 未能获得认证信息
		return null;
	}

	public SessionDetails getSession(HttpServletRequest request) {
		// get session from thread local
		SessionDetails details = _th_session.get();
		if (details != null) {
			return details;
		}

		log.error("调用错误：线程上下文应该存在用户信息");
		throw new RuntimeException("调用错误：线程上下文应该存在用户信息");
	}

	public SessionDetails newSession(HttpServletRequest request, PlatUser user) {
		final HttpSession session = request.getSession();

		final SessionDetails details = newSessionDetails(user);

		session.setAttribute(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, user.getLoginName());
		session.setAttribute(Constants.SESSION_DETAILS, details);

		return details;
	}

	public SessionDetails newSessionDetails(PlatUser user) {
		final String token = TokenUtil.getToken(user.getSalt(), user.getId() + SPLIT + user.getLoginName(),
				user.getPassword(), SessionTimeOut * 10 * 1000);

		final SessionDetails details = new SessionDetails();

		details.setToken(token);
		details.setCurUserId(user.getId());
		details.setCurUser(user);

		return details;
	}

	public void deleteSession(HttpServletRequest request) {
		HttpSession session = request.getSession(false);
		if (session != null) {
			session.invalidate();
		}
	}

	protected SessionDetails authenticateByApiKey(HttpServletRequest request) {
		// authenticate by request
		try {
			String apiKey = request.getHeader("X-API-KEY");
			if (apiKey == null) {
				return null;
			}

			final Cache cache = cacheManager.getCache("X-API-KEY");
			ValueWrapper vw = cache.get(apiKey);
			if (vw != null) {
				log.info("hit cache for x-api-key: {}", apiKey);
				return (SessionDetails) vw.get();
			}

			String email = getEmailByApiKey(apiKey);
			if (email == null) {
				return null;
			}
			PlatUser user = platUserService.getUserByEmail(email);
			if (user == null) {
				return null;
			}
			SessionDetails details = newSessionDetails(user);
			cache.put(apiKey, details);
			log.info("使用 X-API-KEY 登录成功：{}", details.getCurUser().getEmail());
			return details;
		} catch (Exception e) {
			log.error(e.getLocalizedMessage(), e);

		}

		return null;
	}

	private String getEmailByApiKey(String apiKey) {
		String url = getUserEmailByApikeyURL + "?apikey=" + apiKey;
		HttpRequest request = cn.hutool.http.HttpUtil.createGet(url).header("Content-Type", "application/json");
		try (HttpResponse response = request.execute();) {
			String jsonStr = response.body();
			return JSONUtil.parseObj(jsonStr).getStr("result");
		} catch (Exception e) {
			log.error(e.getLocalizedMessage(), e);
		}
		return null;
	}

	@Override
	public PlatSys getSystemFromRequest(HttpServletRequest request) {
		// get system from header
		String sysCode = request.getHeader("mamis-sys-code");
		if (StrUtil.isNotBlank(sysCode)) {
			PlatSys sys = platSysService.getSysByCode(sysCode);
			if (sys != null) {
				_th_platsys.set(sys);
				return sys;
			}
		}

		// get system from cname
		String host = request.getHeader("host");
		if (StrUtil.isNotBlank(host)) {
			final Cache cache = cacheManager.getCache("CNAME");
			return cache.get(host, () -> {
				PlatSys sys = platSysService.getSysByCName(host);
				if (sys != null) {
					_th_platsys.set(sys);
				}
				return sys;
			});
		}

		// fail to get system
		return null;
	}

	public PlatSys setSystemByCode(String sysCode) {
		if (StrUtil.isNotBlank(sysCode)) {
			PlatSys sys = platSysService.getSysByCode(sysCode);
			if (sys != null) {
				_th_platsys.set(sys);
				return sys;
			}
		} else {
			_th_platsys.set(null);
		}
		return null;
	}

	public PlatSys getSystem() {
		// get system from thread local
		PlatSys sys = _th_platsys.get();
		if (sys != null) {
			return sys;
		}

		log.error("调用错误：线程上下文应该存在系统信息");
		throw new RuntimeException("调用错误：线程上下文应该存在系统信息");
	}

	@Override
	public PlatSys setSystemById(String sysId) {
		if (StrUtil.isNotBlank(sysId)) {
			PlatSys sys = platSysService.getSysById(sysId);
			if (sys != null) {
				_th_platsys.set(sys);
				return sys;
			}
		} else {
			_th_platsys.set(null);
		}
		return null;
	}
}
